/*
	Copyright (C) 2009 Salil Bhagurkar
	
	This file is part of ViSE
	
	ViSE is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	ViSE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.
	
	You should have received a copy of the GNU Lesser General Public License
	along with ViSE.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <shared.h>


/* SuperVGA information block */
struct vbeinfo
{
	u8		VESASignature[4];       /* 'VESA' 4 byte signature          */
	u16		VESAVersion;            /* VBE version number               */
	u32		OEMStringPtr;      /* Pointer to OEM string            */
	u32		Capabilities;           /* Capabilities of video card       */
	u32		VideoModePtr;     /* Pointer to supported modes       */
	u16		TotalMemory;            /* Number of 64kb memory blocks     */
	u16		oemver;
	u32		vendorname;
	u32		productname;
	u8		productrevision;
	u16 		vbeafver;
	u32		accelmodes;
	u8		vbe[216];
	u8		oemscratch[256];
}__attribute__((packed));

/* SuperVGA mode information block */
struct vbemodeinfo
{
    u16 ModeAttributes;      /* Mode attributes                 */
    u8  WinAAttributes;      /* Window A attributes             */
    u8  WinBAttributes;      /* Window B attributes             */
    u16 WinGranularity;      /* Window granularity in k         */
    u16 WinSize;             /* Window size in k                */
    u16 WinASegment;         /* Window A segment                */
    u16 WinBSegment;         /* Window B segment                */
    void (*WinFuncPtr)(void);       /* Pointer to window function      */
    u16 BytesPerScanLine;    /* Bytes per scanline              */
    u16 XResolution;         /* Horizontal resolution           */
    u16 YResolution;         /* Vertical resolution             */
    u8  XCharSize;           /* Character cell width            */
    u8  YCharSize;           /* Character cell height           */
    u8  NumberOfPlanes;      /* Number of memory planes         */
    u8  BitsPerPixel;        /* Bits per pixel                  */
    u8  NumberOfBanks;       /* Number of CGA style banks       */
    u8  MemoryModel;         /* Memory model type               */
    u8  BankSize;            /* Size of CGA style banks         */
    u8  NumberOfImagePages;  /* Number of images pages          */
    u8  res1;                /* Reserved                        */
    u8  RedMaskSize;         /* Size of direct color red mask   */
    u8  RedFieldPosition;    /* Bit posn of lsb of red mask     */
    u8  GreenMaskSize;       /* Size of direct color green mask */
    u8  GreenFieldPosition;  /* Bit posn of lsb of green mask   */
    u8  BlueMaskSize;        /* Size of direct color blue mask  */
    u8  BlueFieldPosition;   /* Bit posn of lsb of blue mask    */
    u8  RsvdMaskSize;        /* Size of direct color res mask   */
    u8  RsvdFieldPosition;   /* Bit posn of lsb of res mask     */
    u8  DirectColorModeInfo; /* Direct color mode attributes    */
    u32 phys_base;
    u32 offscreen_start;
    u16 offscreen_memory;
    u16 bytesperscanlin;
    u8 n_image_banked;
    u8 n_image_linear;
    u8 lin_red_mask;
    u8 lin_red_mask_shift;
    u8 lin_green_mask;
    u8 lin_green_mask_shift;
    u8 lin_blue_mask;
    u8 lin_blue_mask_shift;
    u8 lin_res_mask;
    u8 lin_res_mask_shift;
    u32 max_pixel_clock;
    u8 reserved[190];    
} __attribute__((packed));



//Mode information block, pointing to the actual in the bios.. Please make local copies of this.
static struct vbemodeinfo *ModeInfoBlock;
//VBE full info block
static struct vbeinfo *VbeInfoBlock;
//Physical base address of the current mode
static u8 *phys_base;
//Current bank
static int curbank;
static int bankshift;
static int current_mode;
static int prev_mode;


int vbe_get_info()
{
	struct regs regs={0};
	VbeInfoBlock=(void *)0x700;
	regs.eax=0x4f00;
	regs.ebx=0;
	regs.ecx=0;
	regs.edx=0;
	regs.esi=0;
	regs.edi=0x700;
	regs.f=0x10;
	arch_bios_int(&regs);
	if(((u16)regs.eax)!=0x4f)
		return ENOSUP;
	return 0;
}

static int vbe_get_mode_info(int mode)
{
	struct regs regs;
	ModeInfoBlock=(void *)0x800;
	if (mode < 0x100) return ENOSUP;
	
	regs.eax=0x4f01;
	regs.ecx=mode;
	regs.edi=0x0800;
	regs.f=0x10;
	arch_bios_int(&regs);
	
	if (regs.eax != 0x4F) return ENOSUP;
	return 0;
}

/* Set a VBE video mode */
static int vbe_set_mode(int mode)
{
    struct regs regs;
	regs.eax=0x4f03;
	regs.ebx=0;
	regs.f=0x10;
	arch_bios_int(&regs);
	if((u8)(regs.eax)!=0x4f) { //Should this screw up?? (checking prev mode is not so important)
		return ENOSUP;
	}
	prev_mode=regs.ebx;
	printk("\nSetting mode:0x%x",mode);
	regs.eax = 0x4F02; regs.ebx = mode;
	regs.f=0x10;
	arch_bios_int(&regs);
	if((u8)(regs.eax)!=0x4f) {
		return ENOSUP;
	}
	current_mode=mode;
	return 0;
}

static int vbe_reset_mode()
{
	struct regs regs;
	regs.eax = 0x3; regs.ebx = 0;
	regs.f=0x10;
	arch_bios_int(&regs);
	current_mode=prev_mode;
	return 0;
}

/* Return the current VBE video mode */
static int vbe_get_mode(void)
{
    struct regs regs;
    regs.eax=0x4f03;
	regs.f=0x10;
    arch_bios_int(&regs);
	if(((u16)regs.eax)!=0x4f)
		return ENOSUP;
    return regs.ebx;
}

/*The video buffer generally starts at 0xa0000 (0xa000:0x0000). But you can access only
64 kB of it since the length of a segment is 64 kB. So VESA provides a function which allows
you to write in different locations of the screen with the same video memory..
You just need to change the bank according to your need.

Further vbe has some LFBs thats Linear frame buffers. Which are only accessible in pmode
and are of the range 0xe0000000-0xf0000000... They can be directly accessed without
a bank change..*/

/* Set new read/write bank. Set both Window A and Window B, as many VBE's have
 * these set as separately available read and write windows. We also use a 
 * simple (but very effective) optimization of checking if the requested bank 
 * is currently active. */
static int vbe_set_bank(u32 bank)
{
    struct regs regs;
	if(bank > 48 || bank < 0 )
		return EPINV;
    if (bank == curbank) return 0;    /* Bank is already active           */
    curbank = bank;                 /* Save current bank number         */
    bank <<= bankshift;             /* Adjust to window granularity     */
    regs.eax = 0x4F05;
    regs.ebx = 0; 
    regs.edx = bank;
	regs.f=0x10;
    arch_bios_int(&regs);
    regs.eax = 0x4F05;
    regs.ebx = 1; 
    regs.edx = bank;
	regs.f=0x10;
    arch_bios_int(&regs);
	return 0;
}

/*A slow putpixel... For gui thingies you never use putpixel... you use memory routines
that allow fast transfers..*/
static void vbe_putpixel(u32 x,u32 y,u32 color)
{
    u32 addr = (u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
    vbe_set_bank((u16)(addr >> 16));
    switch(ModeInfoBlock->BitsPerPixel)	{
    	case 32:
    	*(phys_base + (addr & 0xFFFF)) = (char)(color);
    	*(phys_base + (addr & 0xFFFF)+1) = (char)(color>>8);
    	*(phys_base + (addr & 0xFFFF)+2) = (char)(color>>16);
    	*(phys_base + (addr & 0xFFFF)+3) = (char)(color>>24);
    	break;
	case 16:
	*(phys_base + (addr & 0xFFFF))=(char)(color);
	*(phys_base + (addr & 0xFFFF)+1)=(char)(color >> 8);
	break;
    	case 8:
    	*(phys_base + (addr & 0xFFFF)) = (char)(color);
    	break;
    } 
}
/*Gets color of a pixel*/
static u32 vbe_getpixel(u32 x,u32 y)
{
	u32 *ph,addr = (u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
	vbe_set_bank((int)(addr >> 16));
	ph=(u32 *)phys_base;
	return *(ph+addr/((ModeInfoBlock->BitsPerPixel)/8));
}

/*These fns are relatively fast functions for Linear Frame Buffer Modes*/
inline void vbe_putlfb(u32 x,u32 y,u32 color)
{
	u32 *ph,addr=(u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
	ph=(u32 *)ModeInfoBlock->phys_base;
	*(ph+addr/((ModeInfoBlock->BitsPerPixel)/8))=color;
}

u32 vbe_getlfb(u32 x,u32 y)
{
	u32 *ph,addr=(u32)y * ModeInfoBlock->BytesPerScanLine + x*(ModeInfoBlock->BitsPerPixel)/8;
	ph=(u32 *)ModeInfoBlock->phys_base;
	return *(ph+addr/((ModeInfoBlock->BitsPerPixel)/8));
}

static u32 flat(u32 ptr)
{
	u16 seg,off;
	u32 flat;
	off=(u16)(ptr);
    seg=(u16)((ptr)>>16);
    flat=seg*0x10+off;
    return flat;
}


enum {MM_TEXT=0,MM_CGA,MM_HGC,MM_EGA16,MM_PACKED,MM_SEQU256,MM_DIRECT,MM_YUV};

char *memory_models[]={"text","cga","hgc","ega_16_color","packed","sequ_256","direct_color","yuv"};

void print_mode_info(int mode)
{
	u16 attr;
	vbe_get_mode_info(mode);
	printk("%dx%d(%x)%dbpp\n",ModeInfoBlock->XResolution,ModeInfoBlock->YResolution,mode,ModeInfoBlock->BitsPerPixel);
	attr=ModeInfoBlock->ModeAttributes;
	
	if(attr & 1)
		printk(":supported");
	if(attr & (1<<3))
		printk(":color");
	else
		printk(":monochrome");
	if(!(attr & (1<<4)))
		printk(":text");
	if(attr & (1<<6))
		printk(":noBSW");
	if(attr & (1<<7))
		printk(":lfb");
	printk("memory_model=");
	if(ModeInfoBlock->MemoryModel < 0x8)
		printk("%s",memory_models[ModeInfoBlock->MemoryModel]);
	else if(ModeInfoBlock->MemoryModel < 0x10)
		printk("reserved_vesa");
	else
		printk("oem");
	printk(",bpsl=%d,bpp=%d\n",ModeInfoBlock->BytesPerScanLine,ModeInfoBlock->BitsPerPixel);
}


static u16 vbe_find_mode(u32 x, u32 y)
{
	u16 *mode;
	u16 ret_mode = 0;
	u16 ret_bpp = 8;
	
	for(mode=(u16 *)flat(VbeInfoBlock->VideoModePtr);;mode++) {
		if((*mode)==0xffff)
			break;
		vbe_get_mode_info(*mode);
		if(ModeInfoBlock->XResolution == x && ModeInfoBlock->YResolution) {
			if(ret_bpp < ModeInfoBlock->BitsPerPixel) {
				ret_mode = *mode;
				ret_bpp = ModeInfoBlock->BitsPerPixel;
			}
		}
	}
	
	printk("Found mode: %x\n", ret_mode);
	
	return ret_mode;
}

static int vbe_init_mode(u16 mode)
{
	u32 i;
	u16 attr;
	if(mode > 0x5000) {
		return EPINV;
	}
	i=vbe_get_mode_info(mode);	
	if(!i && (ModeInfoBlock->ModeAttributes & 0x81)==0x81 && ModeInfoBlock->MemoryModel==MM_DIRECT)	{
		phys_base=(u8 *)0xa0000;
	    	bankshift = 0;
	        while ((u32)(64 >> bankshift) != ModeInfoBlock->WinGranularity)	{
	        	bankshift++;
	    	}
		if(!vbe_get_mode_info(mode | 0x4000)) { //Set lfb if available
			mode=mode | 0x4000;
			vbe_set_mode(mode);
			phys_base = (u8 *)ModeInfoBlock->phys_base;
		} else {
			vbe_set_mode(mode);
		}
		current_mode = mode;
	    	return 0;
	}
    return ENOSUP;
}


int vbe_dev_write(struct node_fs *this, void *vbuf, u32 offset, u32 count)
{
	u16 mode;
	u16 x,y;
	u32 *buf = (u32 *)vbuf;
	if(!buf) { //find mode
		mode = vbe_find_mode(offset, count);
		if(mode) {
			vbe_init_mode(mode);
			return 0;
		}
		return ENODEV;
	} else {//perform write to the buffer
		if(current_mode & 0x4000) {
			if(ModeInfoBlock->BitsPerPixel == 32)
				memcpy(phys_base, buf, count);
			else {
				for(y=0; y<ModeInfoBlock->YResolution; y++) {
					for(x=0; x<ModeInfoBlock->XResolution; x++) {
						vbe_putlfb(x,y,*(buf++));
					}
				}
			}
		} else {
			for(y=0; y<ModeInfoBlock->YResolution; y++) {
				for(x=0; x<ModeInfoBlock->XResolution; x++) {
					vbe_putpixel(x,y,*(buf++));
				}
			}
		}
	}
	return 0;
}

struct node_fs vbe_dev = {
	.type = TYPE_FS_CLIENT,
	.name = "vbe",
	NODE_FS_INIT_VARS,
	NODE_FS_INIT_INIT,
	.read = dummy,
	.write = vbe_dev_write,
	.enumerate = dummy,
	.notify = dummy,
	NODE_FS_LIST_INIT,
};


int vbe_read(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	
	return 0;
}

int vbe_write(struct node_fs *this, void *buf, u32 offset, u32 count)
{
	u16 mode;
	int err = 0;
	switch(offset) {
		case DRIVER_START:
			/*Detect VBE*/
			printk("Detecting VBE\n");
			err=vbe_get_info();
			if(!err) {
				fs_link("/class/graphics", &vbe_dev);
			}
			break;
		case DRIVER_STOP:
			break;
		default:
			return ENOSUP;
	}
	return err;
}


struct node_fs vbe_driver = {
	.name = "vbe_driver",
	.type = TYPE_FS_BOTH,
	NODE_FS_INIT_VARS,
	.class = NULL,
	.read = vbe_read,
	.write = vbe_write,
	.enumerate = dummy,
	.notify = dummy,
	NODE_FS_INIT_INIT,
	NODE_FS_LIST_INIT,
};


int register_vbe()
{
	fs_link("/driver", &vbe_driver);
	return 0;
}
